package com.tencent.txcc.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.tencent.txcc.CourseService;
import com.tencent.txcc.domain.Course;
import com.tencent.txcc.domain.CourseMarket;
import com.tencent.txcc.domain.CourseOrder;
import com.tencent.txcc.domain.CourseOrderItem;
import com.tencent.txcc.dto.*;
import com.tencent.txcc.enums.GlobleExceptionCode;
import com.tencent.txcc.exception.BusinessException;
import com.tencent.txcc.mapper.CourseOrderMapper;
import com.tencent.txcc.result.JSONResult;
import com.tencent.txcc.service.ICourseOrderItemService;
import com.tencent.txcc.service.ICourseOrderService;
import com.tencent.txcc.util.AssertUtil;
import com.tencent.txcc.util.CodeGenerateUtils;
import com.tencent.txcc.vo.CourseInfoVo;
import com.tencent.txcc.vo.CourseOrderVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author director
 * @since 2022-10-16
 */
@Slf4j
@Service
public class CourseOrderServiceImpl extends ServiceImpl<CourseOrderMapper, CourseOrder> implements ICourseOrderService {

    private String redisOrderToken = "orderToken:%s:%s";

    @Autowired
    private ICourseOrderService courseOrderService;

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private CourseService courseService;

    @Autowired
    private ICourseOrderItemService courseOrderItemService;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    /**
     * 课程普通下单
     * @param courseOrderDto
     */
    @Override
    public String placeOrder(CourseOrderDto courseOrderDto) {
        List<Long> courseIds = courseOrderDto.getCourseIds();
        String idsStr = courseIds.stream().map(id -> String.valueOf(id)).collect(Collectors.joining(","));
        Integer payType = courseOrderDto.getPayType();
        String token = courseOrderDto.getToken();

        Long loginId = 42L;
        String redisKey = String.format(redisOrderToken, loginId, idsStr);
        String redisToken = redisTemplate.opsForValue().get(redisKey).toString();
        AssertUtil.isNotEmpty(redisToken, GlobleExceptionCode.ERROR);
        boolean boo = token.equals(redisToken);
        AssertUtil.isTrue(boo, GlobleExceptionCode.ERROR);


        JSONResult info = courseService.getInfo(idsStr);
        AssertUtil.isTrue(info.isSuccess(), GlobleExceptionCode.ERROR);
        Object courseInfoDataTmp = info.getData();
        AssertUtil.isNotNull(courseInfoDataTmp, GlobleExceptionCode.ERROR);
        CourseInfoVo courseInfoVo = JSON.parseObject(JSON.toJSONString(courseInfoDataTmp), CourseInfoVo.class);


        Date now = new Date();
        List<CourseOrderVo> courseInfos = courseInfoVo.getCourseInfos();
        BigDecimal totalAmount = courseInfoVo.getTotalAmount();
        CourseOrder courseOrder = new CourseOrder();
        courseOrder.setCreateTime(now);
        courseOrder.setOrderNo(CodeGenerateUtils.generateOrderSn(loginId));
        courseOrder.setPayType(payType);
        courseOrder.setStatusOrder(CourseOrder.WAIT_PAY_STATUS);
        courseOrder.setTotalAmount(totalAmount);
        courseOrder.setTotalCount(courseInfos.size());
        courseOrder.setUserId(loginId);
        courseOrder.setVersion(0);
        // insert(courseOrder);


        String title = "购买课程 [";
        for (int i = 0; i < courseInfos.size(); i++) {
            Course course = courseInfos.get(i).getCourse();
            CourseMarket courseMarket = courseInfos.get(i).getCourseMarket();
            CourseOrderItem courseOrderItem = new CourseOrderItem();
            courseOrderItem.setAmount(courseMarket.getPrice());
            courseOrderItem.setCount(1);
            courseOrderItem.setCourseId(course.getId());
            courseOrderItem.setCourseName(course.getName());
            courseOrderItem.setCoursePic(course.getPic());
            courseOrderItem.setCreateTime(now);
            // courseOrderItem.setOrderId(courseOrder.getId());
            courseOrderItem.setOrderNo(courseOrder.getOrderNo());
            courseOrderItem.setVersion(0);
            courseOrder.getCourseOrderItem().add(courseOrderItem);
            title = title + course.getName() + ";";
        }
        title = title + "] , 支付["+totalAmount+"] 元";
        courseOrder.setTitle(title);
        // updateById(courseOrder);

        PayDto payDto = new PayDto();
        // ==================扩展参数处理
        PayExtParamDto payExtParamDto = new PayExtParamDto();
        payExtParamDto.setCourseIds(idsStr);
        payExtParamDto.setOrderNo(courseOrder.getOrderNo());
        payExtParamDto.setUserId(loginId);
        payDto.setExtParams(JSON.toJSONString(payExtParamDto)); // {"courseIds":"3,9","userId":"3","orderNo":"21312312"}

        // ==================扩展参数处理



        // MQ

        BeanUtils.copyProperties(courseOrder, payDto);
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(payDto)).build();
        TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction("order-group",
                "order-topic:order-tag",
                message,
                JSON.toJSONString(courseOrder));

        if (transactionSendResult.getSendStatus() != SendStatus.SEND_OK){
            throw new BusinessException(GlobleExceptionCode.ERROR);
        }

        // 判断发送消息是否失败
        boolean sendOk = transactionSendResult.getSendStatus() == SendStatus.SEND_OK;
        AssertUtil.isTrue(sendOk, GlobleExceptionCode.ERROR);

        // 删除防重Token
        redisTemplate.delete(redisKey);

        try {
            // 发送延迟消息
            Message<String> delayMessage = MessageBuilder.withPayload(courseOrder.getOrderNo()).build();
            // String destination, Message<?> message, long timeout, int delayLevel
            SendResult sendResult = rocketMQTemplate.syncSend(
                    "course-order-handler-delay-topic:course-order-handler-delay-tag",
                    delayMessage,
                    2000,
                    3);// 测试设置30S，线上设置30分钟
            if (!sendResult.getSendStatus().equals(SendStatus.SEND_OK)){
                // 重试三次，如果还是不行，那么进行记录，给管理员......发消息,手动处理
                log.error("下单发送延迟消息失败，订单号为：{}", courseOrder.getOrderNo());
            }

        } catch (Exception e) {
            // 如果有异常，那么进行记录，给管理员......发消息
            log.error("下单发送延迟消息失败，订单号为：{}", courseOrder.getOrderNo());
            e.printStackTrace();
        }


        // 响应订单编号
        return courseOrder.getOrderNo();
    }

    @Override
    public CourseOrder selectByOrderNo(String orderNo) {
        Wrapper<CourseOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("order_no", orderNo);
        return courseOrderService.selectOne(wrapper);
    }

    @Override
    public void addCourseOrderAndCourseOrderItem(CourseOrder courseOrder) {
        // 通过订单号获取订单信息
        CourseOrder order = selectByOrderNo(courseOrder.getOrderNo());
        // 如果没下单，进行下单业务
        if (order == null) {
            courseOrderService.insert(courseOrder);
            List<CourseOrderItem> courseOrderItem = courseOrder.getCourseOrderItem();
            courseOrderItem.forEach(e -> {
                e.setOrderId(courseOrder.getId());
                courseOrderItemService.insert(e);
            });
        }
    }

    @Override
    public String killPlaceOrder(KillCourseOrderDto killCourseOrderDto) {
        Long courseId = killCourseOrderDto.getCourseId();
        Integer payType = killCourseOrderDto.getPayType();
        String token = killCourseOrderDto.getToken();
        String orderNo = killCourseOrderDto.getOrderNo();
        // 1.校验交给JSR303
        // 2.防重校验
        Long loginId = 42L; // @TODO 需要修改用户Id
        String redisKey = String.format(redisOrderToken, loginId, courseId);
        Object tokenTmp = redisTemplate.opsForValue().get(redisKey);
        AssertUtil.isNotNull(tokenTmp, GlobleExceptionCode.ERROR);
        boolean boo = token.equals(tokenTmp.toString());
        AssertUtil.isTrue(boo, GlobleExceptionCode.ERROR);

        // 3.去Redis中获取预创订单
        Object o = redisTemplate.opsForValue().get(orderNo);
        AssertUtil.isNotNull(o, GlobleExceptionCode.ERROR);
        KillPreOrderParamDto killPreOrderParamDto = JSON.parseObject(JSON.toJSONString(o), KillPreOrderParamDto.class);

        // 4.保存主订单和订单快照
        Date now = new Date();
        CourseOrder courseOrder = new CourseOrder();
        courseOrder.setCreateTime(now);
        courseOrder.setOrderNo(orderNo);
        courseOrder.setPayType(payType);
        courseOrder.setStatusOrder(CourseOrder.WAIT_PAY_STATUS);
        courseOrder.setTotalAmount(killPreOrderParamDto.getTotalAmount());
        courseOrder.setTotalCount(killPreOrderParamDto.getTotalCount());
        courseOrder.setUserId(loginId);
        courseOrder.setVersion(0);


        CourseOrderItem courseOrderItem = new CourseOrderItem();
        courseOrderItem.setAmount(killPreOrderParamDto.getTotalAmount());
        courseOrderItem.setCount(1);
        courseOrderItem.setCourseId(killPreOrderParamDto.getKillCourseId());
        courseOrderItem.setCourseName(killPreOrderParamDto.getCourseName());
        courseOrderItem.setCoursePic(killPreOrderParamDto.getCoursePic());
        courseOrderItem.setCreateTime(now);
        courseOrderItem.setOrderNo(orderNo);
        courseOrderItem.setVersion(0);
        courseOrder.getCourseOrderItem().add(courseOrderItem);

        String title = "购买课程 ["+killPreOrderParamDto.getCourseName()+"] , 支付["+killPreOrderParamDto.getTotalAmount()+"] 元";
        courseOrder.setTitle(title);

        PayDto payDto = new PayDto();
        BeanUtils.copyProperties(courseOrder, payDto);


        // ==================扩展参数处理
        PayExtParamDto payExtParamsDto = new PayExtParamDto();
        payExtParamsDto.setCourseIds(killPreOrderParamDto.getCourseId().toString());
        payExtParamsDto.setOrderNo(courseOrder.getOrderNo());
        payExtParamsDto.setUserId(loginId);
        payDto.setExtParams(JSON.toJSONString(payExtParamsDto)); // {"courseIds":"3,9","userId":"3","orderNo":"21312312"}

        // ==================扩展参数处理

        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(payDto)).build();
        // 发送mq事务消息 object：是给监听器执行本地事务的，message是消费方进行消费的 1.订单编号、支付总金额、支付方式、订单标题、
        TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(
                "tx-course-order-group",
                "tx-course-order-topic:tx-course-order-tag",
                message,
                JSON.toJSONString(courseOrder));
        // 判断发送消息是否失败
        boolean sendOk = result.getSendStatus() == SendStatus.SEND_OK;
        AssertUtil.isTrue(sendOk, GlobleExceptionCode.ERROR);

        // 删除防重Token
        redisTemplate.delete(redisKey);

        try {
            // 发送延迟消息
            Message<String> delayMessage = MessageBuilder.withPayload(courseOrder.getOrderNo()).build();
            // String destination, Message<?> message, long timeout, int delayLevel
            SendResult sendResult = rocketMQTemplate.syncSend(
                    "course-order-handler-delay-topic:course-order-handler-delay-tag",
                    delayMessage,
                    2000,
                    3);// 测试设置30S，线上设置30分钟
            if (!sendResult.getSendStatus().equals(SendStatus.SEND_OK)){
                // 重试三次，如果还是不行，那么进行记录，给管理员......发消息,手动处理
                log.error("下单发送延迟消息失败，订单号为：{}", courseOrder.getOrderNo());
            }

        } catch (Exception e) {
            // 如果有异常，那么进行记录，给管理员......发消息
            log.error("下单发送延迟消息失败，订单号为：{}", courseOrder.getOrderNo());
            e.printStackTrace();
        }


        // 响应订单编号
        return courseOrder.getOrderNo();
    }
}
